#include "sftpmanager.h"
#include "sshsession.h"
#include <QFileInfo>
#include <QDir>
#include <QDateTime>
#include <QDebug>
#include <QMutex>
#include <QTimer>

SftpManager::SftpManager(SshSession *session, QObject *parent)
    : QObject(parent)
    , m_sshSession(session)
    , m_connected(false)
    , m_sftpSession(nullptr)
    , m_transferTimer(new QTimer(this))
{
    connect(m_transferTimer, &QTimer::timeout, this, &SftpManager::checkTransfers);
    m_transferTimer->start(1000);
}

SftpManager::~SftpManager()
{
    cleanup();
}

bool SftpManager::isConnected() const
{
    return m_connected && m_sftpSession != nullptr;
}

void SftpManager::shutdown()
{
    cleanup();
}

bool SftpManager::initialize()
{
    if (!m_sshSession || !m_sshSession->isConnected()) {
        return false;
    }

    // Initialize SFTP session
    m_sftpSession = libssh2_sftp_init(m_sshSession->sshSession());
    if (!m_sftpSession) {
        qDebug() << "Failed to initialize SFTP session:" << libssh2_sftp_last_error(m_sftpSession);
        return false;
    }

    m_connected = true;
    return true;
}

void SftpManager::cleanup()
{
    // Cancel all active transfers
    for (const QString &transferId : m_activeTransfers.keys()) {
        cancelTransfer(transferId);
    }

    // Close SFTP session
    if (m_sftpSession) {
        libssh2_sftp_shutdown(m_sftpSession);
        m_sftpSession = nullptr;
    }

    m_connected = false;
}

QString SftpManager::getCurrentDirectory() const
{
    return m_currentDirectory;
}

bool SftpManager::changeDirectory(const QString &path)
{
    if (!m_sftpSession) {
        return false;
    }

    LIBSSH2_SFTP_ATTRIBUTES attrs;
    if (libssh2_sftp_stat(m_sftpSession, path.toUtf8().constData(), &attrs) == 0) {
        if (attrs.permissions & LIBSSH2_SFTP_S_IFDIR) {
            m_currentDirectory = path;
            return true;
        }
    }
    return false;
}

QList<SftpManager::DirectoryItem> SftpManager::listDirectory(const QString &path)
{
    QList<DirectoryItem> items;

    if (!m_sftpSession) {
        return items;
    }

    LIBSSH2_SFTP_HANDLE *dir = libssh2_sftp_opendir(m_sftpSession, path.toUtf8().constData());
    if (!dir) {
        qDebug() << "Failed to open directory:" << libssh2_sftp_last_error(m_sftpSession);
        return items;
    }

    LIBSSH2_SFTP_ATTRIBUTES attrs;
    char filename[512];
    ssize_t entry;

    while ((entry = libssh2_sftp_readdir(dir, filename, sizeof(filename), &attrs)) > 0) {
        DirectoryItem item;
        item.name = QString::fromUtf8(filename);
        item.path = path + "/" + item.name;
        item.size = attrs.filesize;
        item.isDirectory = (attrs.permissions & LIBSSH2_SFTP_S_IFDIR) != 0;
        item.isFile = (attrs.permissions & LIBSSH2_SFTP_S_IFREG) != 0;
        item.isSymlink = (attrs.permissions & LIBSSH2_SFTP_S_IFLNK) != 0;
        item.permissions = formatPermissions(attrs.permissions);
        item.lastModified = QDateTime::fromTime_t(attrs.mtime);

        items.append(item);
    }

    libssh2_sftp_closedir(dir);
    emit directoryListed(path, items);
    return items;
}

bool SftpManager::createDirectory(const QString &path)
{
    if (!m_sftpSession) {
        return false;
    }

    int rc = libssh2_sftp_mkdir(m_sftpSession, path.toUtf8().constData(), 0755);
    if (rc != 0) {
        qDebug() << "Failed to create directory:" << libssh2_sftp_last_error(m_sftpSession);
        return false;
    }

    return true;
}

bool SftpManager::removeDirectory(const QString &path, bool recursive)
{
    if (!m_sftpSession) {
        return false;
    }

    if (recursive) {
        QList<DirectoryItem> items = listDirectory(path);
        for (const DirectoryItem &item : items) {
            if (item.isDirectory && item.name != "." && item.name != "..") {
                if (!removeDirectory(item.path, true)) {
                    return false;
                }
            } else if (item.isFile) {
                if (!deleteFile(item.path)) {
                    return false;
                }
            }
        }
    }

    int rc = libssh2_sftp_rmdir(m_sftpSession, path.toUtf8().constData());
    if (rc != 0) {
        qDebug() << "Failed to remove directory:" << libssh2_sftp_last_error(m_sftpSession);
        return false;
    }

    return true;
}

bool SftpManager::removeFile(const QString &path)
{
    if (!m_sftpSession) {
        return false;
    }

    int rc = libssh2_sftp_unlink(m_sftpSession, path.toUtf8().constData());
    if (rc != 0) {
        qDebug() << "Failed to remove file:" << libssh2_sftp_last_error(m_sftpSession);
        return false;
    }

    return true;
}

bool SftpManager::renameFile(const QString &oldPath, const QString &newPath)
{
    if (!m_sftpSession) {
        return false;
    }

    int rc = libssh2_sftp_rename(m_sftpSession, oldPath.toUtf8().constData(), newPath.toUtf8().constData());
    if (rc != 0) {
        qDebug() << "Failed to rename file:" << libssh2_sftp_last_error(m_sftpSession);
        return false;
    }

    return true;
}

bool SftpManager::copyFile(const QString &sourcePath, const QString &destPath)
{
    // For SFTP, we need to download and then upload
    QString tempPath = QDir::tempPath() + "/sftp_temp_" + QFileInfo(sourcePath).fileName();
    if (!downloadFile(sourcePath, tempPath)) {
        return false;
    }

    bool result = uploadFile(tempPath, destPath);
    QFile::remove(tempPath);
    return result;
}

qint64 SftpManager::getFileSize(const QString &remotePath) const
{
    if (!m_sftpSession) {
        return -1;
    }

    LIBSSH2_SFTP_ATTRIBUTES attrs;
    int rc = libssh2_sftp_stat(m_sftpSession, remotePath.toUtf8().constData(), &attrs);
    if (rc != 0) {
        qDebug() << "Failed to get file size:" << libssh2_sftp_last_error(m_sftpSession);
        return -1;
    }

    return attrs.filesize;
}

QDateTime SftpManager::getLastModified(const QString &remotePath) const
{
    if (!m_sftpSession) {
        return QDateTime();
    }

    LIBSSH2_SFTP_ATTRIBUTES attrs;
    int rc = libssh2_sftp_stat(m_sftpSession, remotePath.toUtf8().constData(), &attrs);
    if (rc != 0) {
        qDebug() << "Failed to get file modified time:" << libssh2_sftp_last_error(m_sftpSession);
        return QDateTime();
    }

    return QDateTime::fromTime_t(attrs.mtime);
}

bool SftpManager::fileExists(const QString &remotePath) const
{
    return getFileSize(remotePath) >= 0;
}

bool SftpManager::chmod(const QString &path, int mode)
{
    if (!m_sftpSession) {
        return false;
    }

    LIBSSH2_SFTP_ATTRIBUTES attrs;
    attrs.permissions = mode;
    attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS;

    int rc = libssh2_sftp_setstat(m_sftpSession, path.toUtf8().constData(), &attrs);
    if (rc != 0) {
        qDebug() << "Failed to chmod:" << libssh2_sftp_last_error(m_sftpSession);
        return false;
    }

    return true;
}

bool SftpManager::chown(const QString &path, const QString &owner, const QString &group)
{
    Q_UNUSED(path);
    Q_UNUSED(owner);
    Q_UNUSED(group);
    qDebug() << "chown not implemented in libssh2";
    return false;
}

QString SftpManager::getPermissions(const QString &path) const
{
    if (!m_sftpSession) {
        return "";
    }

    LIBSSH2_SFTP_ATTRIBUTES attrs;
    if (libssh2_sftp_stat(m_sftpSession, path.toUtf8().constData(), &attrs) == 0) {
        return formatPermissions(attrs.permissions);
    }

    return "";
}

bool SftpManager::createSymlink(const QString &target, const QString &linkPath)
{
    if (!m_sftpSession) {
        return false;
    }

    int rc = libssh2_sftp_symlink(m_sftpSession, const_cast<char*>(target.toUtf8().constData()), const_cast<char*>(linkPath.toUtf8().constData()));
    if (rc != 0) {
        qDebug() << "Failed to create symlink:" << libssh2_sftp_last_error(m_sftpSession);
        return false;
    }

    return true;
}

QString SftpManager::readSymlink(const QString &linkPath)
{
    if (!m_sftpSession) {
        return "";
    }

    char target[PATH_MAX];
    int rc = libssh2_sftp_readlink(m_sftpSession, linkPath.toUtf8().constData(), target, sizeof(target));
    if (rc < 0) {
        qDebug() << "Failed to read symlink:" << libssh2_sftp_last_error(m_sftpSession);
        return "";
    }

    return QString::fromUtf8(target);
}

bool SftpManager::uploadFile(const QString &localPath, const QString &remotePath)
{
    if (!m_sftpSession) {
        emit error("SFTP session not initialized");
        return false;
    }

    QString actualRemotePath = remotePath;
    if (actualRemotePath.isEmpty()) {
        actualRemotePath = m_currentDirectory + "/" + QFileInfo(localPath).fileName();
    }

    QString transferId = generateTransferId();
    FileTransferInfo info;
    info.localPath = localPath;
    info.remotePath = actualRemotePath;
    info.totalSize = QFileInfo(localPath).size();
    info.transferredSize = 0;
    info.isUpload = true;
    info.startTime = QDateTime::currentDateTime();
    info.status = "pending";
    info.speed = 0;

    m_activeTransfers[transferId] = info;
    emit fileTransferStarted(transferId);

    return performUpload(localPath, actualRemotePath, transferId);
}

bool SftpManager::downloadFile(const QString &remotePath, const QString &localPath)
{
    if (!m_sftpSession) {
        emit error("SFTP session not initialized");
        return false;
    }

    QString actualLocalPath = localPath;
    if (actualLocalPath.isEmpty()) {
        actualLocalPath = QFileInfo(remotePath).fileName();
    }

    QString transferId = generateTransferId();
    FileTransferInfo info;
    info.localPath = actualLocalPath;
    info.remotePath = remotePath;
    info.totalSize = getFileSize(remotePath);
    info.transferredSize = 0;
    info.isUpload = false;
    info.startTime = QDateTime::currentDateTime();
    info.status = "pending";
    info.speed = 0;

    m_activeTransfers[transferId] = info;
    emit fileTransferStarted(transferId);

    return performDownload(remotePath, actualLocalPath, transferId);
}

bool SftpManager::deleteFile(const QString &remotePath)
{
    return removeFile(remotePath);
}

QList<SftpManager::FileTransferInfo> SftpManager::getActiveTransfers() const
{
    return m_activeTransfers.values();
}

bool SftpManager::cancelTransfer(const QString &transferId)
{
    if (!m_activeTransfers.contains(transferId)) {
        return false;
    }

    FileTransferInfo &info = m_activeTransfers[transferId];
    info.status = "cancelled";
    info.endTime = QDateTime::currentDateTime();

    emit fileTransferFinished(transferId, false);
    m_activeTransfers.remove(transferId);

    return true;
}

void SftpManager::pauseTransfer(const QString &transferId)
{
    if (m_activeTransfers.contains(transferId)) {
        m_activeTransfers[transferId].status = "paused";
    }
}

void SftpManager::resumeTransfer(const QString &transferId)
{
    if (m_activeTransfers.contains(transferId)) {
        FileTransferInfo &info = m_activeTransfers[transferId];
        if (info.status == "paused") {
            info.status = "transferring";
            if (info.isUpload) {
                performUpload(info.localPath, info.remotePath, transferId);
            } else {
                performDownload(info.remotePath, info.localPath, transferId);
            }
        }
    }
}

bool SftpManager::uploadDirectory(const QString &localDir, const QString &remoteDir)
{
    QString transferId = generateTransferId();
    return performDirectoryUpload(localDir, remoteDir, transferId);
}

bool SftpManager::downloadDirectory(const QString &remoteDir, const QString &localDir)
{
    QString transferId = generateTransferId();
    return performDirectoryDownload(remoteDir, localDir, transferId);
}

QString SftpManager::generateTransferId() const
{
    return QString("transfer_%1_%2").arg(QDateTime::currentMSecsSinceEpoch()).arg(qrand());
}

bool SftpManager::performUpload(const QString &localPath, const QString &remotePath, const QString &transferId)
{
    if (!m_activeTransfers.contains(transferId)) {
        return false;
    }

    FileTransferInfo &info = m_activeTransfers[transferId];
    info.status = "transferring";

    QFile localFile(localPath);
    if (!localFile.open(QIODevice::ReadOnly)) {
        info.status = "failed";
        info.error = "Failed to open local file";
        emit fileTransferError(transferId, info.error);
        return false;
    }

    LIBSSH2_SFTP_HANDLE *file = libssh2_sftp_open(m_sftpSession, remotePath.toUtf8().constData(),
                                                 LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
                                                 0644);
    if (!file) {
        info.status = "failed";
        info.error = "Failed to open remote file";
        emit fileTransferError(transferId, info.error);
        return false;
    }

    char buffer[8192];
    qint64 transferredBytes = 0;
    QDateTime lastUpdateTime = QDateTime::currentDateTime();

    while (!localFile.atEnd()) {
        qint64 bytesRead = localFile.read(buffer, sizeof(buffer));
        if (bytesRead < 0) {
            info.status = "failed";
            info.error = "Failed to read from local file";
            emit fileTransferError(transferId, info.error);
            libssh2_sftp_close(file);
            return false;
        }

        ssize_t bytesWritten = libssh2_sftp_write(file, buffer, bytesRead);
        if (bytesWritten < 0) {
            info.status = "failed";
            info.error = "Failed to write to remote file";
            emit fileTransferError(transferId, info.error);
            libssh2_sftp_close(file);
            return false;
        }

        transferredBytes += bytesWritten;
        info.transferredSize = transferredBytes;

        QDateTime now = QDateTime::currentDateTime();
        if (lastUpdateTime.msecsTo(now) > 100) {
            qint64 msecsDiff = lastUpdateTime.msecsTo(now);
            if (msecsDiff > 0) {
                info.speed = (bytesWritten * 1000) / msecsDiff;
            }
            lastUpdateTime = now;
            emit fileTransferProgress(transferId, transferredBytes, info.totalSize);
        }
    }

    libssh2_sftp_close(file);
    localFile.close();

    info.status = "completed";
    info.endTime = QDateTime::currentDateTime();
    emit fileTransferFinished(transferId, true);
    return true;
}

bool SftpManager::performDownload(const QString &remotePath, const QString &localPath, const QString &transferId)
{
    if (!m_activeTransfers.contains(transferId)) {
        return false;
    }

    FileTransferInfo &info = m_activeTransfers[transferId];
    info.status = "transferring";

    LIBSSH2_SFTP_HANDLE *file = libssh2_sftp_open(m_sftpSession, remotePath.toUtf8().constData(),
                                                 LIBSSH2_FXF_READ, 0);
    if (!file) {
        info.status = "failed";
        info.error = "Failed to open remote file";
        emit fileTransferError(transferId, info.error);
        return false;
    }

    QFile localFile(localPath);
    if (!localFile.open(QIODevice::WriteOnly)) {
        info.status = "failed";
        info.error = "Failed to create local file";
        emit fileTransferError(transferId, info.error);
        libssh2_sftp_close(file);
        return false;
    }

    char buffer[8192];
    qint64 transferredBytes = 0;
    QDateTime lastUpdateTime = QDateTime::currentDateTime();

    while (true) {
        ssize_t bytesRead = libssh2_sftp_read(file, buffer, sizeof(buffer));
        if (bytesRead < 0) {
            info.status = "failed";
            info.error = "Failed to read from remote file";
            emit fileTransferError(transferId, info.error);
            libssh2_sftp_close(file);
            localFile.close();
            return false;
        }

        if (bytesRead == 0) {
            break;
        }

        qint64 bytesWritten = localFile.write(buffer, bytesRead);
        if (bytesWritten < 0) {
            info.status = "failed";
            info.error = "Failed to write to local file";
            emit fileTransferError(transferId, info.error);
            libssh2_sftp_close(file);
            localFile.close();
            return false;
        }

        transferredBytes += bytesWritten;
        info.transferredSize = transferredBytes;

        QDateTime now = QDateTime::currentDateTime();
        if (lastUpdateTime.msecsTo(now) > 100) {
            qint64 msecsDiff = lastUpdateTime.msecsTo(now);
            if (msecsDiff > 0) {
                info.speed = (bytesWritten * 1000) / msecsDiff;
            }
            lastUpdateTime = now;
            emit fileTransferProgress(transferId, transferredBytes, info.totalSize);
        }
    }

    libssh2_sftp_close(file);
    localFile.close();

    info.status = "completed";
    info.endTime = QDateTime::currentDateTime();
    emit fileTransferFinished(transferId, true);
    return true;
}

bool SftpManager::performDirectoryUpload(const QString &localDir, const QString &remoteDir, const QString &transferId)
{
    QDir dir(localDir);
    if (!dir.exists()) {
        return false;
    }

    // Create remote directory
    if (!createDirectory(remoteDir)) {
        return false;
    }

    // Upload all files
    QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
    for (const QString &file : files) {
        QString localPath = localDir + "/" + file;
        QString remotePath = remoteDir + "/" + file;
        if (!uploadFile(localPath, remotePath)) {
            return false;
        }
    }

    // Recursively upload subdirectories
    QStringList subdirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
    for (const QString &subdir : subdirs) {
        QString localSubdir = localDir + "/" + subdir;
        QString remoteSubdir = remoteDir + "/" + subdir;
        if (!performDirectoryUpload(localSubdir, remoteSubdir, transferId)) {
            return false;
        }
    }

    return true;
}

bool SftpManager::performDirectoryDownload(const QString &remoteDir, const QString &localDir, const QString &transferId)
{
    QList<DirectoryItem> items = listDirectory(remoteDir);

    // Create local directory
    QDir().mkpath(localDir);

    for (const DirectoryItem &item : items) {
        if (item.name == "." || item.name == "..") {
            continue;
        }

        QString localPath = localDir + "/" + item.name;

        if (item.isDirectory) {
            if (!performDirectoryDownload(item.path, localPath, transferId)) {
                return false;
            }
        } else if (item.isFile) {
            if (!downloadFile(item.path, localPath)) {
                return false;
            }
        }
    }

    return true;
}

void SftpManager::checkTransfers()
{
    // Check for stalled transfers and update speeds
    for (auto it = m_activeTransfers.begin(); it != m_activeTransfers.end(); ++it) {
        FileTransferInfo &info = it.value();
        if (info.status == "transferring") {
            QDateTime now = QDateTime::currentDateTime();
            qint64 msecsDiff = info.startTime.msecsTo(now);
            if (msecsDiff > 0 && info.transferredSize > 0) {
                info.speed = (info.transferredSize * 1000) / msecsDiff;
            }
        }
    }
}

void SftpManager::onTransferProgress()
{
    // Update transfer progress
}

bool SftpManager::validateLocalPath(const QString &path) const
{
    QFileInfo info(path);
    return info.exists();
}

bool SftpManager::validateRemotePath(const QString &path) const
{
    Q_UNUSED(path);
    return true;
}

QString SftpManager::getRelativePath(const QString &basePath, const QString &filePath) const
{
    return QDir(basePath).relativeFilePath(filePath);
}

QString SftpManager::formatPermissions(int mode) const
{
    QString result;

    // File type
    if (mode & LIBSSH2_SFTP_S_IFDIR) {
        result += "d";
    } else if (mode & LIBSSH2_SFTP_S_IFLNK) {
        result += "l";
    } else {
        result += "-";
    }

    // Owner permissions
    result += (mode & LIBSSH2_SFTP_S_IRUSR) ? "r" : "-";
    result += (mode & LIBSSH2_SFTP_S_IWUSR) ? "w" : "-";
    result += (mode & LIBSSH2_SFTP_S_IXUSR) ? "x" : "-";

    // Group permissions
    result += (mode & LIBSSH2_SFTP_S_IRGRP) ? "r" : "-";
    result += (mode & LIBSSH2_SFTP_S_IWGRP) ? "w" : "-";
    result += (mode & LIBSSH2_SFTP_S_IXGRP) ? "x" : "-";

    // Other permissions
    result += (mode & LIBSSH2_SFTP_S_IROTH) ? "r" : "-";
    result += (mode & LIBSSH2_SFTP_S_IWOTH) ? "w" : "-";
    result += (mode & LIBSSH2_SFTP_S_IXOTH) ? "x" : "-";

    return result;
}

QString SftpManager::formatSize(qint64 size) const
{
    const qint64 KB = 1024;
    const qint64 MB = 1024 * KB;
    const qint64 GB = 1024 * MB;

    if (size >= GB) {
        return QString::number(size / double(GB), 'f', 2) + " GB";
    } else if (size >= MB) {
        return QString::number(size / double(MB), 'f', 2) + " MB";
    } else if (size >= KB) {
        return QString::number(size / double(KB), 'f', 2) + " KB";
    } else {
        return QString::number(size) + " B";
    }
}

QString SftpManager::formatSpeed(qint64 bytesPerSec) const
{
    return formatSize(bytesPerSec) + "/s";
}

bool SftpManager::isBinaryFile(const QString &path) const
{
    Q_UNUSED(path);
    return false;
}

void SftpManager::setLastError(const QString &error)
{
    m_lastError = error;
    emit this->error(error);
}

QString SftpManager::getLastSError() const
{
    return m_lastError;
}

SftpManager::DirectoryItem SftpManager::createDirectoryItem(const QString &path, const QString &name)
{
    DirectoryItem item;
    item.name = name;
    item.path = path;
    return item;
}
